package com.vois.jack.btmgr.classicbase;

import static java.lang.System.currentTimeMillis;

import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothSocket;
import android.media.AudioManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;

import com.vois.jack.btmgr.common.DebugMsgStr;
import com.vois.jack.btmgr.common.IState;
import com.vois.jack.btmgr.common.State;
import com.vois.jack.btmgr.common.StateMachine;
import com.vois.jack.btmgr.util.DataRingBuffer;
import com.vois.jack.btmgr.util.Logger;
import com.vois.jack.btmgr.util.TimeoutMonitor;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.Timer;
import java.util.TimerTask;

public class BtDevFsm extends StateMachine {
    private Logger logger = Logger.getLogger(BtDevFsm.class);
    private InitState initState;
    private OpenState openState;
    private CloseState closeState;
    private A2DPProfileFsm a2DPProfileFsm;
    private HeadsetProfileFsm headsetProfileFsm;
    private BtDevice btDevice;
    private BtHelper btHelper;
    private DataRingBuffer dataRingBuffer;
    private int fsmInitStatus = 0;

    private final int A2DP_SUBFSM       = 0x01;
    private final int HEADSET_SUBFSM    = 0x02;

    static private final int A2DP_FSM_INITED = 0x01;
    static private final int HEADSET_FSM_INITED = 0x02;

    static public final int A2DP_ERROR_TYPE     = 0x01;
    static public final int SPP_ERROR_TYPE      = 0x02;
    static public final int SCO_ERROR_TYPE      = 0x03;
    static public final int HEADSET_ERROR_TYPE  = 0x04;

    class InitState extends State {
        @Override
        public String getName() {
            return "InitState";
        }

        @Override
        public void enter() {
            logger.d( "BtDevFsm enter: InitState");
            transitionTo(openState);
        }

        @Override
        public void exit() {
            logger.d( "BtDevFsm exit: InitState");
        }
    }

    class OpenState extends State {
        @Override
        public String getName() {
            return "OpenState";
        }

        @Override
        public void enter() {
            logger.d( "BtDevFsm enter: OpenState");
            a2DPProfileFsm = new A2DPProfileFsm(getHandler());
            headsetProfileFsm = new HeadsetProfileFsm(getHandler());
            a2DPProfileFsm.start();
            headsetProfileFsm.start();
        }

        @Override
        public void exit() {
            logger.d( "BtDevFsm exit: OpenState");
            a2DPProfileFsm.stopFsm();
            headsetProfileFsm.stopFsm();
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                logger.e("OpenState msg what:" + msg.what);
                return ret;
            }

            BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
            logger.d( "BtDevFsm processMessage OpenState: " + btFsmMsg);
            switch (btFsmMsg) {
                case BT_FSM_STOP_MSG: {
                    transitionTo(closeState);
                    ret = true;
                }
                break;
            }
            return ret;
        }
    }

    class CloseState extends State {
        @Override
        public String getName() {
            return "CloseState";
        }

        @Override
        public void enter() {
            logger.d( "enter: CloseState");
        }

        @Override
        public void exit() {
            logger.d( "exit: CloseState");
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                logger.e("CloseState msg what:" + msg.what);
                return ret;
            }

            BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
            switch (btFsmMsg) {
                case BT_FSM_SUB_FSM_CLOSED: {
                    int subFsm = msg.arg1;
                    if (subFsm == A2DP_SUBFSM) {
                        a2DPProfileFsm = null;
                    }else if (subFsm == HEADSET_SUBFSM) {
                        headsetProfileFsm = null;
                    }

                    if (a2DPProfileFsm == null && headsetProfileFsm == null) {
                        transitionToHaltingState();
                        quitNow();
                    }

                    ret = true;
                }
                break;
            }

            return ret;
        }
    }

    class A2DPProfileFsm extends StateMachine {
        private static final String TAG = "JC-A2DPProfileFsm";
        private InitState initState;
        private ConnectingState connectingState;
        private ConnectedState connectedState;
        private DisconnectedState disconnectedState;
        private boolean disconnectByPurpose;

        class InitState extends State {
            @Override
            public String getName() {
                return "InitState";
            }

            @Override
            public void enter() {
                logger.d( TAG + "enter: InitState");
                if (btDevice == null) {
                    logger.e(TAG + "btDevice == null");
                    transitionToHaltingState();
                    quitNow();
                    return;
                }

                int state = btHelper.getA2DPState(btDevice.getDevice());
                if (state == BluetoothProfile.STATE_CONNECTING) {
                    transitionTo(connectingState);
                }else if(state == BluetoothProfile.STATE_CONNECTED) {
                    transitionTo(connectedState);
                }else {
                    transitionTo(disconnectedState);
                }

                fsmInitStatus |= A2DP_FSM_INITED;
                if (fsmInitStatus == 0x03) {
                    btDevice.onInit();
                }
            }

            @Override
            public void exit() {
                logger.d( TAG + "exit: InitState");
            }

            @Override
            public boolean processMessage(Message msg) {
                boolean ret = false;
                if (msg.what < 0) {
                    logger.e(TAG + "InitState msg what:" + msg.what);
                    return ret;
                }

                BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];

                switch (btFsmMsg) {
                    case BT_FSM_STOP_MSG: {
                        transitionToHaltingState();
                        quitNow();
                        ret = true;
                    }
                    break;
                }

                return ret;
            }
        }

        class ConnectingState extends State {
            private TimeoutMonitor timeoutMonitor;
            @Override
            public String getName() {
                return "ConnectingState";
            }

            @Override
            public void enter() {
                logger.d( TAG + "enter: ConnectingState");
                if (btDevice != null) {
                    timeoutMonitor = new TimeoutMonitor(btDevice.getConnectTimeout(), 3, 1000, null, new TimeoutMonitor.MonitorCallback() {
                        @Override
                        public void doAction(Bundle arg) {
                            sendMessage(BtFsmMsg.BT_FSM_RECONNECT_MSG.ordinal());
                        }

                        @Override
                        public void onTimeout() {
                            sendMessage(BtFsmMsg.BT_FSM_CONNECT_TIMEOUT_MSG.ordinal());
                        }
                    });

                    timeoutMonitor.startMonitor();

                    btDevice.onBtConnectStateChanged(BluetoothProfile.A2DP, BluetoothProfile.STATE_CONNECTING);
                }
            }

            @Override
            public void exit() {
                logger.d( TAG + "exit: ConnectingState");
                if (timeoutMonitor != null) {
                    timeoutMonitor.stopMonitor();
                    timeoutMonitor.stopRepeat();
                    timeoutMonitor = null;
                }
            }

            @Override
            public boolean processMessage(Message msg) {
                boolean ret = false;
                if (msg.what < 0) {
                    logger.e(TAG + "ConnectingState msg what:" + msg.what);
                    return ret;
                }

                BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
                logger.d( TAG + "processMessage ConnectingState: " + btFsmMsg);
                switch (btFsmMsg) {
                    case BT_FSM_CONNECT_STATE_CHANGED_MSG: {
                        int state = msg.arg1;
                        logger.d( TAG + "BT_FSM_CONNECT_STATE_CHANGED_MSG: " + DebugMsgStr.valueOf(state));

                        if (state == BluetoothProfile.STATE_CONNECTED) {
                            transitionTo(connectedState);
                        }else if (state == BluetoothProfile.STATE_DISCONNECTED) {
                            transitionTo(disconnectedState);
                        }

                        ret = true;
                    }
                    break;

                    case BT_FSM_RECONNECT_MSG: {
                        if (btDevice != null) {
                            btHelper.connectA2DP(btDevice.getDevice());
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_CONNECT_TIMEOUT_MSG: {
                        if (!timeoutMonitor.doRepeat()) {
                            if (btDevice != null) {
                                btDevice.onBtError(A2DP_ERROR_TYPE, BtErrorCode.ERR_CONNECT_TIMEOUT);
                                btHelper.disconnectA2DP(btDevice.getDevice());
                            }
                            transitionTo(disconnectedState);
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_DISCONNECT_MSG: {
                        if (btDevice != null) {
                            btHelper.disconnectA2DP(btDevice.getDevice());
                        }
                        ret = true;
                    }
                    break;
                }

                return ret;
            }
        }

        class ConnectedState extends State {
            @Override
            public String getName() {
                return "ConnectedState";
            }

            @Override
            public void enter() {
                logger.d( TAG + "enter: ConnectedState");
                if (btDevice != null) {
                    btDevice.onBtConnectStateChanged(BluetoothProfile.A2DP, BluetoothProfile.STATE_CONNECTED);
                }
            }

            @Override
            public void exit() {
                logger.d( TAG + "exit: ConnectedState");
            }

            @Override
            public boolean processMessage(Message msg) {
                boolean ret = false;
                if (msg.what < 0) {
                    logger.e(TAG + "ConnectedState msg what:" + msg.what);
                    return ret;
                }

                BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
                logger.d( TAG + "processMessage ConnectedState: " + btFsmMsg);
                switch (btFsmMsg) {
                    case BT_FSM_CONNECT_STATE_CHANGED_MSG: {
                        int state = msg.arg1;
                        logger.d( TAG + "BT_FSM_CONNECT_STATE_CHANGED_MSG: " + DebugMsgStr.valueOf(state));
                        if (state == BluetoothProfile.STATE_DISCONNECTED) {
                            if (disconnectByPurpose) {
                                deferMessage(obtainMessage(BtFsmMsg.BT_FSM_RECONNECT_MSG.ordinal()));
                                disconnectByPurpose = false;
                            }
                            transitionTo(disconnectedState);
                        }

                        ret = true;
                    }
                    break;

                    case BT_FSM_DISCONNECT_MSG: {
                        if (btDevice != null) {
                            btHelper.disconnectA2DP(btDevice.getDevice());
                        }
                        disconnectByPurpose = true;
                        ret = true;
                    }
                    break;
                }

                return ret;
            }
        }

        class DisconnectedState extends State {
            @Override
            public String getName() {
                return "DisconnectedState";
            }

            @Override
            public void enter() {
                logger.d( TAG + "enter: DisconnectedState");
                if (btDevice != null) {
                    btDevice.onBtConnectStateChanged(BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED);
                }
            }

            @Override
            public void exit() {
                logger.d( TAG + "exit: DisconnectedState");
            }

            @Override
            public boolean processMessage(Message msg) {
                boolean ret = false;
                if (msg.what < 0) {
                    logger.e( TAG + "DisconnectedState msg what:" + msg.what);
                    return ret;
                }
                BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
                logger.d( TAG + "processMessage DisconnectedState:" + btFsmMsg );
                switch (btFsmMsg) {
                    case BT_FSM_CONNECT_STATE_CHANGED_MSG: {
                        int state = msg.arg1;
                        logger.d( TAG + "BT_FSM_CONNECT_STATE_CHANGED_MSG: " + DebugMsgStr.valueOf(state));

                        if (state == BluetoothProfile.STATE_CONNECTED) {
                            transitionTo(connectedState);
                        }else if (state == BluetoothProfile.STATE_CONNECTING) {
                            transitionTo(connectingState);
                        }

                        ret = true;
                    }
                    break;

                    case BT_FSM_RECONNECT_MSG: {
                        deferMessage(msg);
                        transitionTo(connectingState);
                        ret = true;
                    }
                    break;
                }
                return ret;
            }
        }

        A2DPProfileFsm(Handler handler) {
            super("A2DP", handler);
            initState = new InitState();
            connectingState = new ConnectingState();
            connectedState = new ConnectedState();
            disconnectedState = new DisconnectedState();

            addState(initState);
            addState(connectingState, initState);
            addState(connectedState, initState);
            addState(disconnectedState, initState);

            setInitialState(initState);
        }

        void stopFsm() {
            sendMessage(BtFsmMsg.BT_FSM_STOP_MSG.ordinal());
        }

        @Override
        protected void onQuitting() {
            BtDevFsm.this.sendMessage(BtFsmMsg.BT_FSM_SUB_FSM_CLOSED.ordinal(), A2DP_SUBFSM);
        }
    }

    class HeadsetProfileFsm extends StateMachine {
        private static final String TAG = "JC-HeadsetProfileFsm";
        private InitState initState;
        private ConnectingState connectingState;
        private ConnectedState connectedState;
        private DisconnectedState disconnectedState;
        private SPPReadThread readThread;
        private SPPWriteThread writeThread;
        private LinkedList<Integer> scoActionQueue;
        private int currentScoAction;
        private boolean disconnectByPurpose;
        private boolean isSppConnected;
        private boolean isConnected;
        private int sppTry;
        private static final int MAX_SPP_TRY = 100;
        private boolean stopSppByPurpose;
        private boolean isScoConnected;
        private int sppReadWriteReadyFlag;

        final int NONE_SCO_ACTION   =   0x00;
        final int OPEN_SCO_ACTION   =   0x01;
        final int CLOSE_SCO_ACTION  =   0x02;

        class SPPReadThread extends Thread {
            private boolean started;
            private InputStream inputStream;
            private byte[] readBuf;
            private int maxPacketSize;

            public SPPReadThread() {
                super();
                setName("SPPREAD");
            }

            void startRead(BluetoothSocket socket) {
                if (!started && socket != null && btDevice != null) {
                    try {
                        inputStream = socket.getInputStream();
                        maxPacketSize = btDevice.getMaxSppPacketDataSize();
                        readBuf = new byte[maxPacketSize];
                        started = true;
                        stopSppByPurpose = false;
                        start();
                    }catch (IOException e) {
                        e.printStackTrace();
                        btDevice.onBtError(SPP_ERROR_TYPE, BtErrorCode.ERR_SPP_SOCKET_IO);
                        sendMessage(BtFsmMsg.BT_FSM_SPP_GET_INPUTSTREAM_ERROR_MSG.ordinal());
                    }
                }
            }

            @Override
            public void run() {
                int readSize;
                sendMessage(BtFsmMsg.BT_FSM_SPP_READ_READY_MSG.ordinal());
                while (started) {
                    try {
                        long now = System.currentTimeMillis();
                        readSize = inputStream.read(readBuf, 0, maxPacketSize);
                        logger.d(String.format("read size:%d", readSize) + " duration: " + (System.currentTimeMillis() - now));
                        if (!started || isInterrupted()) {
                            break;
                        }

                        if (readSize > 0 && btDevice != null) {
                            logger.dumpArray(readBuf, readSize);
                            btDevice.onSppDataRecv(readBuf, readSize);
                        }
                    }catch (IOException e) {
                        e.printStackTrace();
                        logger.e("read thread err!!!!! " + e.getMessage());
                        if (e.getMessage() != null && !e.getMessage().contains("bt socket closed")) {
                            if (btDevice != null) {
                                btDevice.onBtError(SPP_ERROR_TYPE, BtErrorCode.ERR_SPP_SOCKET_IO);
                            }
                            sendMessage(BtFsmMsg.BT_FSM_SPP_READ_ERROR_MSG.ordinal());
                        }else {
                            sendMessage(BtFsmMsg.BT_FSM_SPP_SOCKET_CLOSED_MSG.ordinal());
                        }

                        break;
                    }
                }
            }

            void stopRead() {
                if (started) {
                    started = false;
                    stopSppByPurpose = true;
                    if (inputStream != null) {
                        try {
                            inputStream.close();
                            inputStream = null;
                        }catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    interrupt();
                    try {
                        join();
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    readBuf = null;
                }
            }
        }

        class SPPWriteThread extends Thread {
            private boolean started;
            private OutputStream outputStream;
            private byte[] writeBuf;
            private int maxPacketSize;

            void startWrite(BluetoothSocket socket) {
                if (!started && socket != null && btDevice != null) {
                    try {
                        outputStream = socket.getOutputStream();
                        maxPacketSize = btDevice.getMaxSppPacketDataSize();
                        writeBuf = new byte[maxPacketSize];
                        started = true;
                        stopSppByPurpose = false;
                        start();
                    }catch (IOException e) {
                        e.printStackTrace();
                        btDevice.onBtError(SPP_ERROR_TYPE, BtErrorCode.ERR_SPP_SOCKET_IO);
                        sendMessage(BtFsmMsg.BT_FSM_SPP_GET_OUTPUTSTREAM_ERROR_MSG.ordinal());
                    }
                }
            }

            @Override
            public void run() {
                int sendSize = 0;
                sendMessage(BtFsmMsg.BT_FSM_SPP_WRITE_READY_MSG.ordinal());
                while (started) {
                    try {
                        sendSize = dataRingBuffer.getData(writeBuf, 0, maxPacketSize);
                        if (!started || isInterrupted()) {
                            break;
                        }

                        if (sendSize > 0) {
                            outputStream.write(writeBuf, 0, sendSize);
                            outputStream.flush();
                        }
                    }catch (IOException | InterruptedException e) {
                        e.printStackTrace();
                        started = false;
                        if (e.getMessage() != null) {
                            logger.e("write thread err!!!! " + e.getMessage());
                        }else {
                            logger.e("Write Thread Err!!!!");
                        }
                        if (e instanceof IOException) {
                            if (e.getMessage() != null && !e.getMessage().contains("bt socket closed")) {
                                if (btDevice != null) {
                                    btDevice.onBtError(SPP_ERROR_TYPE, BtErrorCode.ERR_SPP_SOCKET_IO);
                                }
                                sendMessage(BtFsmMsg.BT_FSM_SPP_WRITE_ERROR_MSG.ordinal());
                            }else {
                                sendMessage(BtFsmMsg.BT_FSM_SPP_SOCKET_CLOSED_MSG.ordinal());
                            }
                        }
                        break;
                    }catch (Exception e) {
                        e.printStackTrace();
                        logger.d("got another exception " + e.getMessage());
                    }
                }
            }

            void stopWrite() {
                if (started) {
                    started = false;
                    stopSppByPurpose = true;
                    if (outputStream != null) {
                        try {
                            outputStream.close();
                            outputStream = null;
                        }catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                    interrupt();
                    try {
                        join();
                    }catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    writeBuf = null;
                }
            }
        }

        class InitState extends State {
            @Override
            public String getName() {
                return "InitState";
            }

            @Override
            public void enter() {
                logger.d( "enter: InitState");
                if (btDevice == null) {
                    logger.e("InitState btDevice == null");
                    transitionToHaltingState();
                    quitNow();
                    return;
                }
                int state = btHelper.getHeadsetState(btDevice.getDevice());
                logger.d( TAG + "InitState: " + DebugMsgStr.valueOf(state));

                if (state == BluetoothProfile.STATE_CONNECTED) {
                    transitionTo(connectedState);
                }else if (state == BluetoothProfile.STATE_CONNECTING) {
                    transitionTo(connectingState);
                }else {
                    transitionTo(disconnectedState);
                }

                isConnected = false;
                isSppConnected = false;
                sppTry = 0;

                fsmInitStatus |= HEADSET_FSM_INITED;
                if (fsmInitStatus == 0x03) {
                    btDevice.onInit();
                }

                isScoConnected = false;
            }

            @Override
            public void exit() {
                logger.d( "exit: InitState");
            }

            @Override
            public boolean processMessage(Message msg) {
                boolean ret = false;
                if (msg.what < 0) {
                    logger.e(TAG + "InitState msg.what:" + msg.what);
                    return ret;
                }

                BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
                logger.d( "processMessage InitState: " + btFsmMsg);
                switch (btFsmMsg) {
                    case BT_FSM_STOP_MSG: {
                        transitionToHaltingState();
                        quitNow();
                        ret = true;
                    }
                    break;
                }
                return ret;
            }
        }

        class ConnectingState extends State {
            private TimeoutMonitor timeoutMonitor;
            @Override
            public String getName() {
                return "ConnectingState";
            }

            @Override
            public void enter() {
                logger.d( "enter: ConnectingState");
                if (btDevice != null) {
                    timeoutMonitor = new TimeoutMonitor(btDevice.getConnectTimeout(), btDevice.getConnectTimeoutTry(), 1000, null, new TimeoutMonitor.MonitorCallback() {
                        @Override
                        public void doAction(Bundle arg) {
                            sendMessage(BtFsmMsg.BT_FSM_RECONNECT_MSG.ordinal());
                        }

                        @Override
                        public void onTimeout() {
                            sendMessage(BtFsmMsg.BT_FSM_CONNECT_TIMEOUT_MSG.ordinal());
                        }
                    });

                    timeoutMonitor.startMonitor();
                    btDevice.onBtConnectStateChanged(BluetoothProfile.HEADSET, BluetoothProfile.STATE_CONNECTING);
                }
            }

            @Override
            public void exit() {
                logger.d( "exit: ConnectingState");
                if (timeoutMonitor != null) {
                    timeoutMonitor.stopMonitor();
                    timeoutMonitor.stopRepeat();
                    timeoutMonitor = null;
                }
            }

            @Override
            public boolean processMessage(Message msg) {
                boolean ret = false;
                if (msg.what < 0) {
                    logger.e(TAG + "ConnectingState msg.what:" + msg.what);
                    return ret;
                }

                BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
                logger.d( "processMessage ConnectingState: " + btFsmMsg);
                switch (btFsmMsg) {
                    case BT_FSM_CONNECT_STATE_CHANGED_MSG: {
                        int state = msg.arg1;
                        logger.d( "BT_FSM_CONNECT_STATE_CHANGED_MSG: " + DebugMsgStr.valueOf(state));
                        if (state == BluetoothProfile.STATE_CONNECTED) {
                            transitionTo(connectedState);
                        }else if (state == BluetoothProfile.STATE_DISCONNECTED) {
                            transitionTo(disconnectedState);
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_RECONNECT_MSG: {
                        if (btDevice != null) {
                            btHelper.connectHeadset(btDevice.getDevice());
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_DISCONNECT_MSG: {
                        if (timeoutMonitor != null) {
                            timeoutMonitor.stopMonitor();
                            timeoutMonitor.stopRepeat();
                            timeoutMonitor = null;
                        }
                        if (btDevice != null) {
                            btHelper.disconnectHeadset(btDevice.getDevice());
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_CONNECT_TIMEOUT_MSG: {
                        if (!timeoutMonitor.doRepeat()) {
                            if (btDevice != null) {
                                btDevice.onBtError(HEADSET_ERROR_TYPE, BtErrorCode.ERR_CONNECT_TIMEOUT);
                                btHelper.disconnectHeadset(btDevice.getDevice());
                            }
                            transitionTo(disconnectedState);
                        }
                        ret = true;
                    }
                    break;
                }
                return ret;
            }
        }

        class ConnectedState extends State {
            private BluetoothSocket bluetoothSocket;
            private SPPReadThread sppReadThread;
            private SPPWriteThread sppWriteThread;
            private Timer scoOpenTimer;
            private Timer scoCloseTimer;
            private final int SCO_TIMEOUT = 3000;
            private final int SCO_CLOSE_TIMEOUT = 2000;

            @Override
            public String getName() {
                return "ConnectedState";
            }

            private boolean connectSocket(BluetoothDevice device) {
                if (btDevice != null) {
                    try {
                        btHelper.cancelDiscovering();
                        bluetoothSocket = device.createRfcommSocketToServiceRecord(btDevice.getSppConnectUUID());
                        bluetoothSocket.connect();
                        return true;
                    } catch (IOException e) {
                        try {
                            e.printStackTrace();
                            bluetoothSocket = device.createInsecureRfcommSocketToServiceRecord(btDevice.getSppConnectUUID());
                            bluetoothSocket.connect();
                        } catch (IOException e2) {
                            e2.printStackTrace();
                            logger.e("connect socket err!!!!!!!!!!!!!");
                            if (bluetoothSocket != null) {
                                btDevice.onBtError(SPP_ERROR_TYPE, BtErrorCode.ERR_SPP_SOCKET_OPEN);
                                try {
                                    bluetoothSocket.close();
                                    bluetoothSocket = null;
                                } catch (IOException e3) {
                                    e3.printStackTrace();
                                }
                            }
                        }
                    }
                }
                return false;
            }

            private void closeSocket() {
                try {
                    if (bluetoothSocket != null) {
                        bluetoothSocket.close();
                    }
                }catch (IOException e) {
                    e.printStackTrace();
                }

                bluetoothSocket = null;
            }

            @Override
            public void enter() {
                logger.d( "enter: ConnectedState");
                scoActionQueue = new LinkedList<>();
                currentScoAction = NONE_SCO_ACTION;
                isConnected = true;
                if (btDevice != null) {
                    btDevice.onBtConnectStateChanged(BluetoothProfile.HEADSET, BluetoothProfile.STATE_CONNECTED);
                }
                isScoConnected = btHelper.isAudioScoOn();
            }

            @Override
            public void exit() {
                logger.d( "exit: ConnectedState");
                isConnected = false;
                isSppConnected = false;
                scoActionQueue.clear();
                scoActionQueue = null;

                isScoConnected = false;

                if (sppWriteThread != null) {
                    sppWriteThread.stopWrite();
                    sppWriteThread = null;
                }

                if (sppReadThread != null) {
                    sppReadThread.stopRead();
                    sppReadThread = null;
                }

                closeSocket();

                if (btHelper.isAudioScoOn()) {
                    btHelper.closeAudioSco();
                }

                if (scoActionQueue != null) {
                    scoActionQueue.clear();
                    scoActionQueue = null;
                }

                if (dataRingBuffer != null) {
                    dataRingBuffer.destroyBuffer();
                    dataRingBuffer = null;
                }
            }

            private void resetAndRetrySpp() {
                if (sppTry < MAX_SPP_TRY) {
                    isSppConnected = false;
                    if (sppWriteThread != null) {
                        sppWriteThread.stopWrite();
                        sppWriteThread = null;
                    }

                    if (sppReadThread != null) {
                        sppReadThread.stopRead();
                        sppReadThread = null;
                    }

                    closeSocket();

                    if (dataRingBuffer != null) {
                        dataRingBuffer.destroyBuffer();
                        dataRingBuffer = null;
                    }
                    sendMessage(BtFsmMsg.BT_FSM_CONNECT_SPP_MSG.ordinal());
                    sppTry++;
                }else{
                    if (btDevice != null) {
                        btDevice.onBtError(SPP_ERROR_TYPE, BtErrorCode.ERR_SPP_TRY_MAX);
                    }
                    sppTry = 0;
                }
            }

            @Override
            public boolean processMessage(Message msg) {
                boolean ret = false;
                if (msg.what < 0) {
                    logger.e(TAG + "ConnectedState msg.what:" + msg.what);
                    return ret;
                }

                BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
                logger.d( "ConnectedState processMessage: " + btFsmMsg);
                switch (btFsmMsg) {
                    case BT_FSM_CONNECT_SPP_MSG: {
                        if (btDevice == null) {
                            break;
                        }

                        if (connectSocket(btDevice.getDevice())) {
                            sppReadWriteReadyFlag = 0;
                            dataRingBuffer = new DataRingBuffer("BtDevFsm", 8912, true);
                            sppReadThread = new SPPReadThread();
                            sppWriteThread = new SPPWriteThread();
                            sendMessage(BtFsmMsg.BT_FSM_SPP_CONNECTING_MSG.ordinal());
                            sppTry = 0;
                        }else {
                            sendMessage(BtFsmMsg.BT_FSM_CONNECT_SPP_ERROR_MSG.ordinal());
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_SPP_READ_READY_MSG: {
                        sppReadWriteReadyFlag |= 0x01;
                        if (sppReadWriteReadyFlag == 0x03) {
                            sendMessage(BtFsmMsg.BT_FSM_SPP_CONNECTED_MSG.ordinal());
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_SPP_WRITE_READY_MSG: {
                        sppReadWriteReadyFlag |= 0x02;
                        if (sppReadWriteReadyFlag == 0x03) {
                            sendMessage(BtFsmMsg.BT_FSM_SPP_CONNECTED_MSG.ordinal());
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_SPP_CONNECTING_MSG: {
                        if (btDevice != null) {
                            btDevice.onSppSocketConnecting();
                        }
                        isSppConnected = true;
                        if (sppWriteThread != null) {
                            sppWriteThread.startWrite(bluetoothSocket);
                        }

                        if (sppReadThread != null) {
                            sppReadThread.startRead(bluetoothSocket);
                        }

                        ret = true;
                    }
                    break;

                    case BT_FSM_SPP_CONNECTED_MSG: {
                        if (btDevice != null) {
                            btDevice.onSppSocketConnected();
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_DISCONNECT_SPP_MSG: {
                        closeSocket();

                        if (sppWriteThread != null) {
                            sppWriteThread.stopWrite();
                            sppWriteThread = null;
                        }

                        if (sppReadThread != null) {
                            sppReadThread.stopRead();
                            sppReadThread = null;
                        }

                        if (dataRingBuffer != null) {
                            dataRingBuffer.destroyBuffer();
                            dataRingBuffer = null;
                        }
                        isSppConnected = false;
                        ret = true;
                    }
                    break;

                    case BT_FSM_SPP_SOCKET_CLOSED_MSG: {
                        if (btDevice != null) {
                            //btDevice.onSppSocketDisconnected();
                            btDevice.onSppSocketErrorOrClose();
                        }

                        ret = true;
                    }
                    break;

                    case BT_FSM_SEND_SPP_DATA_MSG: {
                        if (dataRingBuffer != null) {
                            try {
                                dataRingBuffer.putData((byte[]) msg.obj, 0, msg.arg1);
                            }catch (InterruptedException e) {
                                logger.e("BT_FSM_SEND_SPP_DATA_MSG error:" + e.getMessage());
                                e.printStackTrace();
                            }
                        }else {
                            logger.e("dataRingBuffer is null!!!!");
                        }

                        ret = true;
                    }
                    break;

                    case BT_FSM_CONNECT_STATE_CHANGED_MSG: {
                        int state = msg.arg1;
                        logger.d( "BT_FSM_CONNECT_STATE_CHANGED_MSG: " + DebugMsgStr.valueOf(state));
                        if (state == BluetoothProfile.STATE_DISCONNECTED) {
                            if (!disconnectByPurpose) {
                                deferMessage(obtainMessage(BtFsmMsg.BT_FSM_RECONNECT_MSG.ordinal()));
                                disconnectByPurpose = false;
                            }
                            transitionTo(disconnectedState);
                        }

                        ret = true;
                    }
                    break;

                    case BT_FSM_SCO_ADD_MSG: {
                        int newScoCommand = msg.arg1;
                        int nextScoCommand = NONE_SCO_ACTION;
                        ret = true;

                        if (!scoActionQueue.isEmpty()) {
                            try {
                                nextScoCommand = scoActionQueue.getLast();
                            } catch (NoSuchElementException e) {
                                e.printStackTrace();
                            }
                        }

                        if (newScoCommand == nextScoCommand) {
                            break;
                        }

                        scoActionQueue.offer(newScoCommand);

                        if (currentScoAction == NONE_SCO_ACTION) {
                            sendMessage(BtFsmMsg.BT_FSM_SCO_EXECUTE_COMMAND_MSG.ordinal());
                        }
                    }
                    break;

                    case BT_FSM_SCO_EXECUTE_COMMAND_MSG: {
                        if (btDevice == null) {
                            currentScoAction = NONE_SCO_ACTION;
                            ret = true;
                            break;
                        }

                        if (scoActionQueue.isEmpty()) {
                            currentScoAction = NONE_SCO_ACTION;
                            ret = true;
                            break;
                        }

                        currentScoAction = scoActionQueue.poll();

                        logger.d( "BT_FSM_SCO_EXECUTE_COMMAND_MSG: " + currentScoAction);
                        logger.d( "BT_FSM_SCO_EXECUTE_COMMAND_MSG: isScoOn:" + btHelper.isAudioScoOn() + " isScoConnected:" + isScoConnected);

                        if (currentScoAction == OPEN_SCO_ACTION) {
                            if (!btHelper.isAudioScoOn() || !isScoConnected) {
                                btHelper.openAudioSco(btDevice.getScoAudioMode());
                                if (scoOpenTimer != null) {
                                    scoOpenTimer.cancel();
                                }

                                scoOpenTimer = new Timer();
                                scoOpenTimer.schedule(new TimerTask() {
                                    @Override
                                    public void run() {
                                        sendMessage(BtFsmMsg.BT_FSM_SCO_EXECUTE_TIMEOUT_MSG.ordinal());
                                    }
                                }, SCO_TIMEOUT);
                            }else {
                                currentScoAction = NONE_SCO_ACTION;
                                sendMessage(BtFsmMsg.BT_FSM_SCO_EXECUTE_COMMAND_MSG.ordinal());
                            }
                        }else {
                            if (btHelper.isAudioScoOn() || isScoConnected) {
                                btHelper.closeAudioSco();
                                if (scoCloseTimer != null) {
                                    scoCloseTimer.cancel();
                                }

                                scoCloseTimer = new Timer();
                                scoCloseTimer.schedule(new TimerTask() {
                                    @Override
                                    public void run() {
                                        sendMessage(BtFsmMsg.BT_FSM_SCO_EXECUTE_CLOSE_TIMEOUT_MSG.ordinal());
                                    }
                                }, SCO_CLOSE_TIMEOUT);
                            }else {
                                currentScoAction = NONE_SCO_ACTION;
                                sendMessage(BtFsmMsg.BT_FSM_SCO_EXECUTE_COMMAND_MSG.ordinal());
                            }
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_SCO_EXECUTE_CLOSE_TIMEOUT_MSG: {
                        isScoConnected = false;
                        currentScoAction = NONE_SCO_ACTION;
                        sendMessage(BtFsmMsg.BT_FSM_SCO_EXECUTE_COMMAND_MSG.ordinal());
                    }
                    break;

                    case BT_FSM_SCO_EXECUTE_TIMEOUT_MSG: {
                        if (scoOpenTimer != null) {
                            scoOpenTimer.cancel();
                            scoOpenTimer = null;
                        }

                        btHelper.closeAudioSco();
                        if (btDevice != null) {
                            btDevice.onBtError(SCO_ERROR_TYPE, BtErrorCode.ERR_SCO_OPEN_TIMEOUT);
                        }
                        currentScoAction = NONE_SCO_ACTION;
                        scoActionQueue.clear();

                        ret = true;
                    }
                    break;

                    case BT_FSM_SCO_STATE_CHANGED_MSG: {
                        int state = msg.arg1;
                        if (btDevice == null) {
                            break;
                        }
                        ret = true;
                        btDevice.onScoStateChanged(state);
                        if (state == AudioManager.SCO_AUDIO_STATE_CONNECTED) {
                            isScoConnected = true;
                            if (scoOpenTimer != null) {
                                scoOpenTimer.cancel();
                                scoOpenTimer = null;
                            }

                            if (currentScoAction == CLOSE_SCO_ACTION) {
                                btHelper.closeAudioSco();
                                break;
                            }

                            currentScoAction = NONE_SCO_ACTION;
                            sendMessage(BtFsmMsg.BT_FSM_SCO_EXECUTE_COMMAND_MSG.ordinal());
                        }else if (state == AudioManager.SCO_AUDIO_STATE_DISCONNECTED) {
                            isScoConnected = false;
                            if (scoCloseTimer != null) {
                                scoCloseTimer.cancel();
                                scoCloseTimer = null;
                            }

                            if (currentScoAction == OPEN_SCO_ACTION) {
                                btHelper.openAudioSco(btDevice.getScoAudioMode());
                                if (scoOpenTimer != null) {
                                    scoOpenTimer.cancel();
                                }

                                scoOpenTimer = new Timer();
                                scoOpenTimer.schedule(new TimerTask() {
                                    @Override
                                    public void run() {
                                        sendMessage(BtFsmMsg.BT_FSM_SCO_EXECUTE_TIMEOUT_MSG.ordinal());
                                    }
                                }, SCO_TIMEOUT);
                                break;
                            }

                            currentScoAction = NONE_SCO_ACTION;
                            sendMessage(BtFsmMsg.BT_FSM_SCO_EXECUTE_COMMAND_MSG.ordinal());
                        }

                        ret = true;
                    }
                    break;

                    case BT_FSM_DISCONNECT_MSG: {
                        if (btDevice == null) {
                            break;
                        }
                        disconnectByPurpose = true;
                        btHelper.disconnectHeadset(btDevice.getDevice());
                        ret = true;
                    }
                    break;

                    //processing spp error
                    case BT_FSM_CONNECT_SPP_ERROR_MSG: {
                        if (btHelper.getBluetoothState() == BluetoothAdapter.STATE_ON) {
                            resetAndRetrySpp();
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_SPP_GET_INPUTSTREAM_ERROR_MSG:
                    case BT_FSM_SPP_GET_OUTPUTSTREAM_ERROR_MSG: {
                        if (btDevice != null) {
                            btDevice.onSppSocketDisconnected();
                        }
                        if (btHelper.getBluetoothState() == BluetoothAdapter.STATE_ON) {
                            resetAndRetrySpp();
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_SPP_READ_ERROR_MSG: {
                        if (btDevice != null) {
                            btDevice.onSppSocketDisconnected();
                        }
                        if (btHelper.getBluetoothState() == BluetoothAdapter.STATE_ON) {
                            resetAndRetrySpp();
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_SPP_WRITE_ERROR_MSG: {
                        if (btDevice != null) {
                            btDevice.onSppSocketDisconnected();
                        }
                        if (btHelper.getBluetoothState() == BluetoothAdapter.STATE_ON) {
                            resetAndRetrySpp();
                        }
                        ret = true;
                    }
                    break;
                }

                return ret;
            }
        }

        class DisconnectedState extends State {
            @Override
            public String getName() {
                return "DisconnectedState";
            }

            @Override
            public void enter() {
                logger.d( "enter: DisconnectedState");
                if (btDevice != null) {
                    btDevice.onBtConnectStateChanged(BluetoothProfile.HEADSET, BluetoothProfile.STATE_DISCONNECTED);
                }
            }

            @Override
            public void exit() {
                logger.d( "exit: DisconnectedState");
            }

            @Override
            public boolean processMessage(Message msg) {
                boolean ret = false;
                if (msg.what < 0) {
                    logger.e(TAG + "DisconnectedState msg.what:" + msg.what);
                    return ret;
                }
                BtFsmMsg btFsmMsg = BtFsmMsg.values()[msg.what];
                logger.d( "DisconnectedState processMessage: " + btFsmMsg);
                switch (btFsmMsg) {
                    case BT_FSM_RECONNECT_MSG: {
                        deferMessage(msg);
                        transitionTo(connectingState);
                        ret = true;
                    }
                    break;

                    case BT_FSM_SPP_WRITE_ERROR_MSG:
                    case BT_FSM_SPP_READ_ERROR_MSG:
                    case BT_FSM_SPP_SOCKET_CLOSED_MSG: {
                        if (btDevice != null) {
                            //btDevice.onSppSocketDisconnected();
                            btDevice.onSppSocketErrorOrClose();
                        }
                        ret = true;
                    }
                    break;

                    case BT_FSM_CONNECT_STATE_CHANGED_MSG: {
                        int state = msg.arg1;
                        logger.d( "BT_FSM_CONNECT_STATE_CHANGED_MSG: " + DebugMsgStr.valueOf(state));
                        if (state == BluetoothProfile.STATE_CONNECTING) {
                            transitionTo(connectingState);
                        }else if (state == BluetoothProfile.STATE_CONNECTED) {
                            transitionTo(connectedState);
                        }
                        ret = true;
                    }
                    break;
                }
                return ret;
            }
        }

        boolean isConnected() {
            return headsetProfileFsm.isConnected;
        }

        boolean isSppConnected() {
            return isSppConnected;
        }

        HeadsetProfileFsm(Handler handler) {
            super("HEADSET", handler);
            initState = new InitState();
            connectingState = new ConnectingState();
            connectedState = new ConnectedState();
            disconnectedState = new DisconnectedState();

            addState(initState);
            addState(connectingState, initState);
            addState(connectedState, initState);
            addState(disconnectedState, initState);

            setInitialState(initState);
        }

        void stopFsm() {
            sendMessage(BtFsmMsg.BT_FSM_STOP_MSG.ordinal());
        }

        @Override
        protected void onQuitting() {
            BtDevFsm.this.sendMessage(BtFsmMsg.BT_FSM_SUB_FSM_CLOSED.ordinal(), HEADSET_SUBFSM);
        }
    }

    public BtDevFsm(BtDevice btDevice) {
        super(btDevice.getDevice().getName() != null ? btDevice.getDevice().getName() : btDevice.getDevice().getAddress());
        this.btDevice = btDevice;
        btHelper = BtHelper.getInstance();

        initState = new InitState();
        openState = new OpenState();
        closeState = new CloseState();

        addState(initState);
        addState(openState, initState);
        addState(closeState, initState);
        setInitialState(initState);
    }

    void notifyHeadsetConnectChanged(int curState, int preState) {
        if (headsetProfileFsm != null) {
            headsetProfileFsm.sendMessage(BtFsmMsg.BT_FSM_CONNECT_STATE_CHANGED_MSG.ordinal(), curState, preState);
        }
    }

    void notifyA2DPConnectChanged(int curState, int preState) {
        if (a2DPProfileFsm != null) {
            a2DPProfileFsm.sendMessage(BtFsmMsg.BT_FSM_CONNECT_STATE_CHANGED_MSG.ordinal(), curState, preState);
        }
    }

    void notifyHeadsetScoChanged(int state) {
        if (headsetProfileFsm != null) {
            headsetProfileFsm.sendMessage(BtFsmMsg.BT_FSM_SCO_STATE_CHANGED_MSG.ordinal(), state);
        }
    }

    boolean startConnectSpp() {
        if (headsetProfileFsm != null) {
            logger.d("startConnectSpp: connected?" + headsetProfileFsm.isConnected());
            if (headsetProfileFsm != null && headsetProfileFsm.isConnected()) {
                headsetProfileFsm.sendMessageDelayed(BtFsmMsg.BT_FSM_CONNECT_SPP_MSG.ordinal(), 200);
                return true;
            }
        }

        return false;
    }

    boolean disconnectSpp() {
        if (headsetProfileFsm != null && headsetProfileFsm.isSppConnected()) {
            headsetProfileFsm.sendMessage(BtFsmMsg.BT_FSM_DISCONNECT_SPP_MSG.ordinal());
            return true;
        }

        return false;
    }

    void putSendingData(byte[] buffer, int bufferSize) {
        if (headsetProfileFsm != null) {
            logger.d("putSendingData: buffserSize:" + bufferSize + " spp connected:" + headsetProfileFsm.isSppConnected());

            if (headsetProfileFsm.isSppConnected()) {
                byte[] cacheBuf = new byte[bufferSize];
                System.arraycopy(buffer, 0, cacheBuf, 0, bufferSize);
                headsetProfileFsm.sendMessage(BtFsmMsg.BT_FSM_SEND_SPP_DATA_MSG.ordinal(), bufferSize, 0, cacheBuf);
            }
        }
    }

    boolean startScoAction() {
        if (headsetProfileFsm != null && headsetProfileFsm.isConnected()) {
            headsetProfileFsm.sendMessage(BtFsmMsg.BT_FSM_SCO_ADD_MSG.ordinal(), headsetProfileFsm.OPEN_SCO_ACTION);
            return true;
        }

        return false;
    }

    boolean stopScoAction() {
        if (headsetProfileFsm != null && headsetProfileFsm.isConnected()) {
            headsetProfileFsm.sendMessage(BtFsmMsg.BT_FSM_SCO_ADD_MSG.ordinal(), headsetProfileFsm.CLOSE_SCO_ACTION);
            return true;
        }

        return false;
    }

    void reconnect() {
        if (a2DPProfileFsm != null) {
            a2DPProfileFsm.sendMessage(BtFsmMsg.BT_FSM_RECONNECT_MSG.ordinal());
        }

        if (headsetProfileFsm != null) {
            headsetProfileFsm.sendMessage(BtFsmMsg.BT_FSM_RECONNECT_MSG.ordinal());
        }
    }

    void disconnect() {
        if (a2DPProfileFsm != null) {
            a2DPProfileFsm.sendMessage(BtFsmMsg.BT_FSM_DISCONNECT_MSG.ordinal());
        }

        if (headsetProfileFsm != null) {
            headsetProfileFsm.sendMessage(BtFsmMsg.BT_FSM_DISCONNECT_MSG.ordinal());
        }
    }

    void stopFsm() {
        sendMessage(BtFsmMsg.BT_FSM_STOP_MSG.ordinal());
    }

    @Override
    protected void onQuitting() {
        if (btDevice != null) {
            btDevice.onFsmQuit();
        }
    }

    public IState getCurState() {
        return getCurrentState();
    }

    public void detachDevice() {
        btDevice = null;
    }
}
